/* main.cs

	Infinite Rogue
	By Dan Alexander, Andy Law
	July 2012

	Cross platform roguelike using C#, Mono, WinForms and GDI.
	
	LICENSE: New BSD License
*/

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Text;
using System.IO;
using System.Linq;

public class mainForm : Form
{
    int BUFFERS_WIDE = 40;
    int BUFFERS_HIGH = 24;
    int BUFFER_HEIGHT = 20;
    int BUFFER_WIDTH = 20;
    int SCREEN_WIDTH;
    int SCREEN_HEIGHT;
    Color BUFFER_BACKCOLOR = Color.Black;
    Bitmap SCREEN_BITMAP;
    Dictionary<char, Bitmap> bitmapFont;
    Dictionary<Color, Dictionary<char, Bitmap>> coloredBitmapFont;
    Graphics GRAPHICS;
    Engine engine;
    string currentState = "story";
    StoryInfo _storyInfo;
    private List<string> messages;
    private int messageDisplayCount = 5;

    public mainForm()
    {
        messages = new List<string>();
        Text = "Infinite Rogue";

        string reff = "icon.bmp";
        var assembly = System.Reflection.Assembly.GetEntryAssembly();
        var icon = assembly.GetManifestResourceStream(reff) ?? File.Open("icon.bmp", FileMode.Open);
        Bitmap bmp = new Bitmap(icon);

        try
        {
            this.Icon = Icon.FromHandle(bmp.GetHicon());
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }


        //set up screen size
        SCREEN_WIDTH = BUFFER_WIDTH * BUFFERS_WIDE;
        SCREEN_HEIGHT = BUFFER_HEIGHT * BUFFERS_HIGH;
        this.Width = SCREEN_WIDTH;
        this.Height = SCREEN_HEIGHT;
        //this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
        //this.AutoSize = true;
        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
        this.BackColor = BUFFER_BACKCOLOR;

        //SETUP SCREEN BITMAP
        SCREEN_BITMAP = new Bitmap(SCREEN_WIDTH, SCREEN_HEIGHT);
        GRAPHICS = Graphics.FromImage(SCREEN_BITMAP);

        this.Resize += new EventHandler(mainForm_Resize);

        coloredBitmapFont = new Dictionary<Color, Dictionary<char, Bitmap>>();

        Console.WriteLine("LOADING FONT.PNG");
        LoadBitmapFont("font.png", 48, 6, 20, 20);

        // Subscribe to events (can also be done in designer).
        Console.WriteLine("CREATING INPUT HANDLERS");
        this.KeyDown += new KeyEventHandler(OnKeyDown);
        this.KeyPress += new KeyPressEventHandler(OnKeyPress);
        this.KeyUp += new KeyEventHandler(OnKeyUp);
        this.Load += new EventHandler(OnLoad);
        this.Paint += new PaintEventHandler(mainForm_Paint);
        this.DoubleBuffered = true;
        this.MouseClick += new MouseEventHandler(mainForm_MouseClick);
        CenterToScreen();

        Console.WriteLine("GENERATING STORY");
        _storyInfo = StoryGenerator.NewStory();
        Console.WriteLine("DRAWING STORY");
        DrawStory();
        Console.WriteLine("STARTUP DONE");

        InfiniteRogue.Log.Instance.NewLogEntry += new InfiniteRogue.Log.LogEventHandler(LogInstance_NewLogEntry);
    }

    void LogInstance_NewLogEntry(string message, InfiniteRogue.Log.LogLevel importance)
    {
        if (importance == InfiniteRogue.Log.LogLevel.HighImportance || importance == InfiniteRogue.Log.LogLevel.MediumImportance)
        {
            messages.Add(message);
        }
    }

    void mainForm_MouseClick(object sender, MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            ++BUFFER_HEIGHT;
            ++BUFFER_WIDTH;
        }
        else
        {
            --BUFFER_HEIGHT;
            --BUFFER_WIDTH;
        }
        mainForm_Resize(null, null);
    }

    void mainForm_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.DrawImage(SCREEN_BITMAP, this.ClientRectangle.Location);
    }

    void mainForm_Resize(object sender, EventArgs e)
    {
        BUFFERS_WIDE = this.ClientRectangle.Width / BUFFER_WIDTH;
        BUFFERS_HIGH = this.ClientRectangle.Height / BUFFER_HEIGHT;
        SCREEN_WIDTH = BUFFERS_WIDE * BUFFER_WIDTH;
        SCREEN_HEIGHT = BUFFERS_HIGH * BUFFER_HEIGHT;
        SCREEN_BITMAP = new Bitmap(SCREEN_BITMAP, SCREEN_WIDTH, SCREEN_HEIGHT);
        GRAPHICS = Graphics.FromImage(SCREEN_BITMAP);
        this.Refresh();
    }

    void engine_MapChangedEvent(Engine engine)
    {
        DrawAroundPlayer(engine);
    }

    public void LoadBitmapFont(string filename, int chars_across, int chars_down, int width, int height)
    {
        bitmapFont = new Dictionary<char, Bitmap>();
        Bitmap originalFullImage = LoadImage(filename);

        int cellWidth = width;
        int cellHeight = height;

        //how much extra space is around the data
        int borderX = originalFullImage.Width - (cellWidth * chars_across);
        int borderY = originalFullImage.Height - (cellHeight * chars_down);

        Graphics g = Graphics.FromImage(originalFullImage);

        int startX = borderX / 2;
        int startY = borderY / 2;

        int count = 0;

        int xx = startX;
        int yy = startY;

        for (int y = 0; y < chars_down; ++y)
        {
            for (int x = 0; x < chars_across; ++x)
            {
                Bitmap frame = originalFullImage.Clone(new Rectangle(xx, yy, cellWidth, cellHeight), originalFullImage.PixelFormat);
                bitmapFont.Add((char)count, frame);
                ++count;
                xx += cellWidth;
            }
            xx = startX;
            yy += cellHeight;
        }

    }

    /// <summary>
    /// Ensures that recolloring of bitmaps only happens once, gets a font character in a given color
    /// </summary>
    /// <param name="s">font character</param>
    /// <param name="c">font color</param>
    /// <returns></returns>
    public Bitmap GetColoredFont(char s, Color c)
    {
        if (!coloredBitmapFont.ContainsKey(c))
        {
            Dictionary<char, Bitmap> colored = new Dictionary<char, Bitmap>();
            foreach (KeyValuePair<char, Bitmap> item in bitmapFont)
            {
                colored.Add(item.Key, ColorBitmapFont(item.Value, c));
            }
            coloredBitmapFont.Add(c, colored);
        }
        return coloredBitmapFont[c][s];
    }

    public void TestBitmapFont(Graphics g)
    {
        //TEST BITMAP FONT
        int xx = 0;
        int yy = 0;
        foreach (char key in bitmapFont.Keys)
        {
            PrintBitmapCharacter(key, Color.White, xx, yy);
            xx += 20;
            if (xx > SCREEN_WIDTH - 20)
            {
                xx = 0;
                yy += 20;
            }
        }
    }

    public void PrintBitmapCharacter(char character, Color color, int x, int y)
    {
        GRAPHICS.DrawImage(GetColoredFont(character, color), x, y, BUFFER_WIDTH, BUFFER_HEIGHT);
    }

    public Bitmap AlterColor(Bitmap bmp, Color oldColor, Color newColor)
    {
        for (int y = 0; y < bmp.Height; ++y)
        {
            for (int x = 0; x < bmp.Width; ++x)
            {
                Color orig = bmp.GetPixel(x, y);
                if (orig.A == oldColor.A && orig.B == oldColor.B && orig.G == oldColor.G && orig.R == oldColor.R)
                    bmp.SetPixel(x, y, newColor);
            }
        }
        return bmp;
    }

    /// <summary>
    /// Returns a colored copy of the supplied bitmap
    /// </summary>
    /// <param name="bmp">Bitmap to copy and color</param>
    /// <param name="newColor">Desired color</param>
    /// <returns>A colored copy of the supplied bitmap</returns>
    public Bitmap ColorBitmapFont(Bitmap bmp, Color newColor)
    {
        Bitmap nb = (Bitmap)bmp.Clone();
        return AlterColor(nb, Color.White, newColor);
    }

    public void FloodWithCharacter(string character, Graphics g, int x, int y, int width, int height)
    {
        for (int yy = 0; yy < BUFFERS_HIGH; ++yy)
        {
            for (int xx = 0; xx < BUFFERS_WIDE; ++xx)
            {
                //if (yy == 0 || xx == 0 || yy == BUFFERS_HIGH - 1 || xx == BUFFERS_WIDE - 1)
                // g.DrawString("#", font12, Brushes.Yellow, xx * BUFFER_WIDTH, yy * BUFFER_HEIGHT);
                //else
                //g.DrawString(character.ToString(), font12, Brushes.White, xx * BUFFER_WIDTH, yy * BUFFER_HEIGHT);
            }
        }
    }

    public void DrawAroundPlayer(Engine engine)
    {
        GRAPHICS.FillRectangle(new SolidBrush(BUFFER_BACKCOLOR), 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

        Map _map = engine.Map;
        MapCoord start = engine.Map.DrawStart(engine.Player, BUFFERS_WIDE, BUFFERS_HIGH);

        for (int x = 0; x < BUFFERS_WIDE; ++x)
        {
            for (int y = 0; y < BUFFERS_HIGH - 1; ++y)
            {
                TileData data = engine.TileDataAt(x + start.x, y + start.y);
                if (data.Discovered)
                {
                    PrintBitmapCharacter(data.Character, data.Color, x * BUFFER_WIDTH, y * BUFFER_HEIGHT);
                }
            }
        }

        //draw stats
        string stats = "HP: [HP]/[HPMAX]  MP: [MP]/[MPMAX]  XP: [EXP]  LVL: [LVL]";
        stats = stats.Replace("[HP]", engine.Player.Stats[StatSystem.HP].ToString());
        stats = stats.Replace("[HPMAX]", engine.Player.Stats[StatSystem.HP_MAX].ToString());
        stats = stats.Replace("[EXP]", engine.Player.Stats[StatSystem.EXP].ToString());
        stats = stats.Replace("[LVL]", engine.Player.Stats[StatSystem.LVL].ToString());
        stats = stats.Replace("[MP]", engine.Player.Stats[StatSystem.MP].ToString());
        stats = stats.Replace("[MPMAX]", engine.Player.Stats[StatSystem.MP_MAX].ToString());

        DrawString(stats, Color.Yellow, 1, BUFFERS_HIGH - 1);

        List<string> printed = new List<string>((messages as IEnumerable<string>).Reverse().Take(messageDisplayCount));
        for (int i = 0; i < printed.Count; ++i)
        {
            DrawString(printed[i], Color.White, 1, i + 1);
        }

        this.Refresh();
    }

    public void DrawCharacterInfo()
    {
        GRAPHICS.FillRectangle(new SolidBrush(BUFFER_BACKCOLOR), 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        DrawBorderedBackground((char)220, Color.SaddleBrown, (char)176, Color.SaddleBrown);

        int col1 = 2;
        int col2 = 10;
        int col3 = 20;
        int col4 = 28;

        //PLAYER NAME
        DrawStringCentered(engine.Player.Name, Color.Yellow, 1);

        //Core Stats
        DrawString("HP:", Color.Yellow, col1, 3);
        DrawString("MP:", Color.Yellow, col1, 4);
        DrawString(engine.Player.Stats["HP"].ToString() + "/" + engine.Player.Stats[StatSystem.HP_MAX].ToString(), Color.White, col2, 3);
        DrawString(engine.Player.Stats["MP"].ToString() + "/" + engine.Player.Stats[StatSystem.MP_MAX].ToString(), Color.White, col2, 4);

        //Basic Stats
        DrawString("STR:", Color.Yellow, col1, 6);
        DrawString("AGI:", Color.Yellow, col1, 7);
        DrawString("END:", Color.Yellow, col1, 8);
        DrawString("WIS:", Color.Yellow, col1, 9);
        DrawString("INT:", Color.Yellow, col1, 10);
        DrawString(engine.Player.Stats["STR"].ToString(), Color.White, col2, 6);
        DrawString(engine.Player.Stats["AGI"].ToString(), Color.White, col2, 7);
        DrawString(engine.Player.Stats["END"].ToString(), Color.White, col2, 8);
        DrawString(engine.Player.Stats["WIS"].ToString(), Color.White, col2, 9);
        DrawString(engine.Player.Stats["INT"].ToString(), Color.White, col2, 10);

        //MAGIC:
        DrawString("MAGIC", Color.Yellow, col1, 12);

        DrawString("FIRE", Color.Yellow, col1, 13);
        DrawString("WATER", Color.Yellow, col1, 14);
        DrawString("AIR", Color.Yellow, col1, 15);
        DrawString("EARTH", Color.Yellow, col1, 16);
        DrawString("DARK", Color.Yellow, col1, 17);
        DrawString("LIGHT", Color.Yellow, col1, 18);

        DrawString(engine.Player.Stats["P_FIRE"].ToString(), Color.White, col2, 13);
        DrawString(engine.Player.Stats["P_WATER"].ToString(), Color.White, col2, 14);
        DrawString(engine.Player.Stats["P_AIR"].ToString(), Color.White, col2, 15);
        DrawString(engine.Player.Stats["P_EARTH"].ToString(), Color.White, col2, 16);
        DrawString(engine.Player.Stats["P_DARK"].ToString(), Color.White, col2, 17);
        DrawString(engine.Player.Stats["P_LIGHT"].ToString(), Color.White, col2, 18);

        //MAGIC RESISTANCE
        DrawString("RESISTANCE", Color.Yellow, col3, 12);

        DrawString("FIRE", Color.Yellow, col3, 13);
        DrawString("WATER", Color.Yellow, col3, 14);
        DrawString("AIR", Color.Yellow, col3, 15);
        DrawString("EARTH", Color.Yellow, col3, 16);
        DrawString("DARK", Color.Yellow, col3, 17);
        DrawString("LIGHT", Color.Yellow, col3, 18);

        DrawString(engine.Player.Stats["R_FIRE"].ToString(), Color.White, col4, 13);
        DrawString(engine.Player.Stats["R_WATER"].ToString(), Color.White, col4, 14);
        DrawString(engine.Player.Stats["R_AIR"].ToString(), Color.White, col4, 15);
        DrawString(engine.Player.Stats["R_EARTH"].ToString(), Color.White, col4, 16);
        DrawString(engine.Player.Stats["R_DARK"].ToString(), Color.White, col4, 17);
        DrawString(engine.Player.Stats["R_LIGHT"].ToString(), Color.White, col4, 18);

        //SKILLS
        DrawString("ATTACK:", Color.Yellow, col3, 3);
        DrawString("DODGE:", Color.Yellow, col3, 4);
        DrawString("STUN:", Color.Yellow, col3, 5);
        DrawString("CRIT:", Color.Yellow, col3, 6);
        DrawString("PARRY:", Color.Yellow, col3, 7);
        DrawString("SEARCH:", Color.Yellow, col3, 8);
        DrawString("REGEN:", Color.Yellow, col3, 9);

        DrawString(engine.Player.Stats["ATTACK"].ToString(), Color.White, col4, 3);
        DrawString(engine.Player.Stats["DODGE"].ToString(), Color.White, col4, 4);
        DrawString(engine.Player.Stats["STUN"].ToString(), Color.White, col4, 5);
        DrawString(engine.Player.Stats["CRITICAL"].ToString(), Color.White, col4, 6);
        DrawString(engine.Player.Stats["PARRY"].ToString(), Color.White, col4, 7);
        DrawString(engine.Player.Stats["SEARCH"].ToString(), Color.White, col4, 8);
        DrawString(engine.Player.Stats["REGEN"].ToString(), Color.White, col4, 9);

        //LEVEL
        DrawString("LEVEL:", Color.Yellow, col1, BUFFERS_HIGH - 2);
        DrawString(engine.Player.Stats["LVL"].ToString(), Color.White, col2, BUFFERS_HIGH - 2);

        //XP
        DrawString("XP:", Color.Yellow, col3, BUFFERS_HIGH - 2);
        DrawString(engine.Player.Stats["EXP"].ToString(), Color.White, col4, BUFFERS_HIGH - 2);

        this.Refresh();
    }

    public void DrawString(string stringToDraw, Color color, int bufferX, int bufferY)
    {
        for (int i = 0; i < stringToDraw.Length; ++i)
        {
            PrintBitmapCharacter(stringToDraw[i], color, (bufferX + i) * BUFFER_WIDTH, bufferY * BUFFER_HEIGHT);
        }
    }

    public void DrawStringCentered(string stringToDraw, Color color, int bufferY)
    {
        int x = BUFFERS_WIDE / 2 - stringToDraw.Length / 2;
        for (int i = 0; i < stringToDraw.Length; ++i)
        {
            PrintBitmapCharacter(stringToDraw[i], color, (x + i) * BUFFER_WIDTH, bufferY * BUFFER_HEIGHT);
        }
    }

    public void DrawStory()
    {
        GRAPHICS.FillRectangle(new SolidBrush(BUFFER_BACKCOLOR), 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

        DrawBorderedBackground((char)220, Color.SaddleBrown, (char)176, Color.SaddleBrown);

        string story = _storyInfo.story;

        List<string> lines = new List<string>();
        string currentLine = "";

        string[] words = story.Split(new Char[] { ' ', '\n' });

        foreach (string word in words)
        {
            if (word == "")
            {
                lines.Add(currentLine);
                lines.Add("");
                currentLine = "";
                continue;
            }
            string combined = currentLine + " " + word;
            if (combined.Length < BUFFERS_WIDE - 2)
            {
                currentLine = currentLine + " " + word;
            }
            else
            {
                lines.Add(currentLine.Trim());
                currentLine = word;
            }
        }
        lines.Add(currentLine);
        lines.Add("");
        lines.Add("");
        lines.Add("...press enter to continue...");

        int y = 1;

        foreach (string line in lines)
        {
            int x = 1;
            for (int i = 0; i < line.Length; ++i)
            {
                if (line[i] != '\n')
                {
                    PrintBitmapCharacter(line[i], Color.White, x * BUFFER_WIDTH, y * BUFFER_HEIGHT);
                    ++x;
                }
                else
                {
                    PrintBitmapCharacter('B', Color.White, x * BUFFER_WIDTH, y * BUFFER_HEIGHT);
                    ++y;
                    x = 1;
                }
            }
            ++y;
        }

        this.Refresh();
    }

    public void DrawBorder(char character, Color color)
    {
        for (int x = 0; x < BUFFERS_WIDE; ++x)
        {
            if (x == 0 || x == BUFFERS_WIDE - 1)
            {
                for (int y = 0; y < BUFFERS_HIGH; ++y)
                {
                    PrintBitmapCharacter(character, color, x * BUFFER_WIDTH, y * BUFFER_HEIGHT);
                }
            }
            else
            {
                PrintBitmapCharacter(character, color, x * BUFFER_WIDTH, 0);
                PrintBitmapCharacter(character, color, x * BUFFER_WIDTH, BUFFER_HEIGHT * (BUFFERS_HIGH - 1));
            }
        }
    }

    public void DrawBorderedBackground(char borderCharacter, Color borderColor, char fillCharacter, Color fillColor)
    {
        for (int y = 0; y < BUFFERS_HIGH; ++y)
        {
            for (int x = 0; x < BUFFERS_WIDE; ++x)
            {
                if (x == 0 || y == 0 || x == BUFFERS_WIDE - 1 || y == BUFFERS_HIGH - 1)
                    PrintBitmapCharacter(borderCharacter, borderColor, x * BUFFER_WIDTH, y * BUFFER_HEIGHT);
                else
                    PrintBitmapCharacter(fillCharacter, fillColor, x * BUFFER_WIDTH, y * BUFFER_HEIGHT);
            }
        }
    }

    //THIS TAKES CARE OF THE WEIRD PROBLEM LOADING IMAGES ON MAC OS X.

    public Bitmap LoadImage(string filename)
    {
        var assembly = System.Reflection.Assembly.GetEntryAssembly();
        var icon = assembly.GetManifestResourceStream(filename) ?? File.Open(filename, FileMode.Open);
        Bitmap tempOrig = new Bitmap(icon);

        Bitmap loadedImage = new Bitmap(tempOrig.Width, tempOrig.Height);

        Graphics g = Graphics.FromImage(loadedImage);
        g.DrawImage(tempOrig, 0, 0);

        g.Dispose();
        tempOrig.Dispose();

        return loadedImage;
    }

    //THIS LAUNCHES THE FORM
    static public void Main()
    {
        Application.Run(new mainForm());
    }

    void OnLoad(object sender, EventArgs e)
    {
        // Give the Form focus
        this.Focus();
    }

    private void OnKeyDown(object sender, KeyEventArgs e)
    {
        // Key Press
        //Console.WriteLine(e.KeyCode.ToString());

        if (e.KeyCode == Keys.Escape && (currentState == "playing" || currentState == "story"))
        {
            Application.Exit();
        }

        if (currentState == "playing")
        {
            if (e.KeyCode == Keys.Left)
            {
                engine.MovePlayer(-1, 0);
            }
            else if (e.KeyCode == Keys.Right)
            {
                engine.MovePlayer(1, 0);
            }
            else if (e.KeyCode == Keys.Up)
            {
                engine.MovePlayer(0, -1);
            }
            else if (e.KeyCode == Keys.Down)
            {
                engine.MovePlayer(0, 1);
            }
            else if (e.KeyCode == Keys.Space)
            {
                engine.RestPlayer();
            }
            else if (e.KeyCode == Keys.A)
            {
                engine.PlayerMelee();
            }
        }
    }

    private void OnKeyPress(object sender, KeyPressEventArgs e)
    {
        // Key Press
        //Console.WriteLine(e.KeyChar.ToString());

        if (e.KeyChar.ToString() == ">")
        {
            engine.StairDown();
        }
    }

    private void OnKeyUp(object sender, KeyEventArgs e)
    {
        // Key Press
        //Console.WriteLine("KEY up");
        if (e.KeyCode == Keys.Enter && currentState == "story")
        {
            engine = new Engine();
            engine.MapChangedEvent += new Engine.MapChangedEventHandler(engine_MapChangedEvent);
            DrawAroundPlayer(engine);
            currentState = "playing";
        }
        else if (e.KeyCode == Keys.Tab && currentState == "story")
        {
            _storyInfo = StoryGenerator.NewStory();
            DrawStory();
        }

        if (e.KeyCode == Keys.C && currentState == "playing")
        {
            currentState = "character";
            DrawCharacterInfo();
        }
        if (e.KeyCode == Keys.Escape && currentState == "character")
        {
            currentState = "playing";
            DrawAroundPlayer(engine);
        }
    }
}